package top.guoziyang.mydb.backend.dm;

import top.guoziyang.mydb.backend.common.AbstractCache;
import top.guoziyang.mydb.backend.dm.dataItem.DataItem;
import top.guoziyang.mydb.backend.dm.dataItem.DataItemImpl;
import top.guoziyang.mydb.backend.dm.logger.Logger;
import top.guoziyang.mydb.backend.dm.page.Page;
import top.guoziyang.mydb.backend.dm.page.PageOne;
import top.guoziyang.mydb.backend.dm.page.PageX;
import top.guoziyang.mydb.backend.dm.pageCache.PageCache;
import top.guoziyang.mydb.backend.dm.pageIndex.PageIndex;
import top.guoziyang.mydb.backend.dm.pageIndex.PageInfo;
import top.guoziyang.mydb.backend.tm.TransactionManager;
import top.guoziyang.mydb.backend.utils.Panic;
import top.guoziyang.mydb.backend.utils.Types;
import top.guoziyang.mydb.common.Error;

public class DataManagerImpl extends AbstractCache<DataItem> implements DataManager {

    TransactionManager tm; //事务管理
    PageCache pc; //页面缓存管理
    Logger logger; // 日志管理
    PageIndex pIndex; //页面空闲空间管理
    Page pageOne; //第一页

    public DataManagerImpl(PageCache pc, Logger logger, TransactionManager tm) {
        super(0);
        this.pc = pc;
        this.logger = logger;
        this.tm = tm;
        this.pIndex = new PageIndex();
    }

    /**
     * 从缓存中读取DataItemImpl实例
     */
    @Override
    public DataItem read(long uid) throws Exception {
        DataItemImpl di = (DataItemImpl)super.get(uid);
        // 检查DataItemImpl是否有效（isValid()），如果无效则释放资源
        if(!di.isValid()) {
            di.release();
            return null;
        }
        return di;
    }

    /**
     * 插入数据到缓存数据页中
     */
    @Override
    public long insert(long xid, byte[] data) throws Exception {
        // 将数据打包成DataItem格式的字节集群，用于序列化和存储
        byte[] raw = DataItem.wrapDataItemRaw(data);
        // 如果数据长度超过数据页的最大空闲空间，抛出数据过大异常
        if(raw.length > PageX.MAX_FREE_SPACE) {
            throw Error.DataTooLargeException;
        }
        //尝试获得休闲空间足够的位置（多次尝试直到成功），如果无法找到则创建新页面。
        PageInfo pi = null;
        for(int i = 0; i < 5; i ++) {
            pi = pIndex.select(raw.length);
            if (pi != null) {
                break;
            } else {
                // 当前没有合适的数据页，所以新建一页
                int newPgno = pc.newPage(PageX.initRaw());
                // 将新建页的空闲空间添加到PageIndex中
                pIndex.add(newPgno, PageX.MAX_FREE_SPACE);
            }
        }
        // 如果多次尝试后未能创建新的数据页以供插入数据，表示当前数据库页面空间不足或数据库负载过高，抛出异常
        if(pi == null) {
            throw Error.DatabaseBusyException;
        }
        // 找到有足够空闲空间的数据页后，插入数据
        Page pg = null;
        int freeSpace = 0;
        try {
            // 获取数据页
            pg = pc.getPage(pi.pgno);
            // 生成一个“插入日志”（InsertLog）的字节数组，记录某次插入操作的相关信息
            byte[] log = Recover.insertLog(xid, pg, raw);
            // 把这条插入日志写入日志文件中
            logger.log(log);
            // 将数据插入pg中，并返回插入位置
            short offset = PageX.insert(pg, raw);
            // 释放对页面的资源占用
            pg.release();
            // 返回唯一标识符uid
            return Types.addressToUid(pi.pgno, offset);

        } finally {
            // 将取出的pg重新插入pIndex
            if(pg != null) {
                // pg!=null表示成功完成了insert操作(有空闲页面供插入),重新计算该页的空闲空间更新到PageIndex中
                pIndex.add(pi.pgno, PageX.getFreeSpace(pg));
            } else {
                // pg == null 表示没有成功获取页面pg，那么磁盘和缓存里目前都没有这一页，把这一页的freeSpace设为0，以后的线程拿到这一页后都不能继续插入
                pIndex.add(pi.pgno, freeSpace);
            }
        }
    }

    @Override
    public void close() {
        // 关闭缓存，写回所有资源
        super.close();
        logger.close();
        //数据库正常关闭，将第一页100~107的校验码，拷贝到 108 ~ 115 字节。
        PageOne.setVcClose(pageOne);
        pageOne.release();
        pc.close();
    }

    /**
     * 根据数据项信息和xid,生成update日志,记录某次更新操作的相关信息
     */
    public void logDataItem(long xid, DataItem di) {
        byte[] log = Recover.updateLog(xid, di);
        logger.log(log);
    }

    /**
     * 释放DataItemImpl实例的缓存
     */
    public void releaseDataItem(DataItem di) {
        super.release(di.getUid());
    }

    /**
     * 根据uid从页面缓存中读取DataItem数据项
     */
    @Override
    protected DataItem getForCache(long uid) throws Exception {
        short offset = (short)(uid & ((1L << 16) - 1));
        uid >>>= 32;
        int pgno = (int)(uid & ((1L << 32) - 1));
        Page pg = pc.getPage(pgno);
        return DataItem.parseDataItem(pg, offset, this);
    }

    /**
     * 释放DataItemImpl对应页的缓存
     */
    @Override
    protected void releaseForCache(DataItem di) {
        di.page().release();
    }

    /**
     * 在创建文件时初始化PageOne
     */
    void initPageOne() {
        // PageOne.InitRaw()返回的是一个容量为8KB 的字节数组，调用pc.newPage（）方法创建数据库的第一页
        int pgno = pc.newPage(PageOne.InitRaw());
        // 确保第一页的页面编号为1
        assert pgno == 1;
        try {
            pageOne = pc.getPage(pgno);
        } catch (Exception e) {
            Panic.panic(e);
        }
        // 把 第一页 持久化到磁盘中
        pc.flushPage(pageOne);
    }

    /**
     * 在打开已有文件时时读入PageOne，并验证正确性
     */
    boolean loadCheckPageOne() {
        try {
            pageOne = pc.getPage(1);
        } catch (Exception e) {
            Panic.panic(e);
        }
        return PageOne.checkVc(pageOne);
    }

    /**
     * 初始化pageIndex
     */
    void fillPageIndex() {
        // 获取当前PageCache中有多少页
        int pageNumber = pc.getPageNumber();
        // 从第二页开始获取，是因为第一页存储的是数据库的元数据
        for(int i = 2; i <= pageNumber; i ++) {
            Page pg = null;
            try {
                pg = pc.getPage(i);
            } catch (Exception e) {
                Panic.panic(e);
            }
            // 加入页面休闲空间的索引中
            pIndex.add(pg.getPageNumber(), PageX.getFreeSpace(pg));
            pg.release();
        }
    }
    
}
